/**
 * 
 */
package de.farbstreich.tozen2.server;

import java.io.IOException;
import java.net.Socket;

import de.farbstreich.tozen2.common.EndOfTheStreamException;
import de.farbstreich.tozen2.common.TozenMessageReaderWriter;
import de.farbstreich.tozen2.common.TozenMessageType;
import de.farbstreich.tozen2.common.TozenProtocolMessage;
import de.farbstreich.tozen2.common.TozenProtocolMessage.gameType;
import de.farbstreich.tozen2.server.games.Game;
import de.farbstreich.tozen2.server.games.GameTypeAbstract;
import de.farbstreich.tozen2.server.games.GameWrapper;
import de.farbstreich.tozen2.server.games.NoMorePlayerException;
import de.farbstreich.tozen2.server.games.NotRightPlayerCountException;
import de.farbstreich.tozen2.server.games.Game.TileEnum;

/**
 * @author martin
 * Represents a player ont the server.
 */
public class PlayerThread extends Thread {
	
	//Inner classes
	/**
	 * @author martin
	 * The places, where a player can be
	 */
	public enum PlaceEnum {
		LOGIN, LOBBY, GAME;
	}
	
	//Public properties
	
	//Static propertis
	private static int thisCount = 1;
	
	//Private properties
	/**
	 * The server, which holds this player
	 */
	private TozenServer server;
	private int id;
	private PlaceEnum place;
	private String name;
	private TozenMessageReaderWriter mrw;
	public Game game;
	
	
	
	/**
	 * Constructor
	 * 
	 * @param socket
	 * @param server
	 * @param playerId
	 */
	public PlayerThread(Socket socket, TozenServer server) {
		super();
		try {
			this.mrw = new TozenMessageReaderWriter(socket.getInputStream(),
					socket.getOutputStream());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.server = server;
		this.id = thisCount++;
		this.name = null;
		this.place = PlaceEnum.LOGIN;

	}
	
	/**
	 * <h3>Getter & Setter</h3>
	 */
	
	/**
	 * @return the unique id of this player
	 */
	public int getPlayerId() {
		return id;
	}
	
	public String getPlayerName() {
		return name;
	}
	
	public PlaceEnum getPlace() {
		return place;
	}
	
	
	public void sendProtocolMessage(TozenProtocolMessage msg) {
		this.mrw.send(msg);
	}
	
	/**
	 * Send a message to this player
	 * @param senderId
	 * @param message
	 * @param broadcast
	 */
	public void sendMessage(int senderId, String message, boolean broadcast) {
		TozenProtocolMessage msg = new TozenProtocolMessage(
				TozenMessageType.MESSAGE);
		msg.message = message;
		msg.broadcast = broadcast;
		TozenProtocolMessage.player p = msg.new player();
		p.id = senderId;
		msg.players.add(p);
		mrw.send(msg);
	}
	
	public void sendInfoMessage(String message) {
		this.sendMessage(0, message, false);
	}
	
	synchronized public void goToLobby() {
		if(this.game!=null)
			this.game.removePlayer(this);
		
		this.place = PlaceEnum.LOBBY;
		
		TozenProtocolMessage msg = new TozenProtocolMessage(TozenMessageType.GAME_TYPES);
		
		msg.gameTypes = new gameType[GameWrapper.games.length];
		int k = 0;
		for (GameTypeAbstract g : GameWrapper.games) {
			msg.gameTypes[k] = msg.new gameType();
			msg.gameTypes[k].id = g.id;
			msg.gameTypes[k].title = g.title;
			msg.gameTypes[k].maxPlayersCount = g.maxPlayersCount;
			msg.gameTypes[k++].minPlayersCount = g.minPlayersCount;
		}
		this.mrw.send(msg);
	}
	
	synchronized  public void goToGame(Game g) throws NoMorePlayerException {
		g.addPlayer(this);
		this.place = PlaceEnum.GAME;
		this.game = g;
	}
	
	synchronized  public void kill() {
		this.server.killPlayer(this);
		
	}

	/**
	 * The main method of this thread.
	 */
	@Override
	synchronized public void run() {
		TozenProtocolMessage msg;
		while (true) {
			try {
				msg = mrw.receive();

				if(msg.type != TozenMessageType.PING)
					System.out.println("incomming \"" + msg.type+ "\" message:" + msg.toString());

				if (this.place == PlaceEnum.LOGIN && msg.type != TozenMessageType.SET_NAME) continue;

				switch (msg.type) {
				case SET_NAME:
					handleSetName(msg);
					break;
				case SET_GAME:
					handleSetGame(msg);
					break;
				case MESSAGE:
					handleMessage(msg);
					break;
				case JOIN_GAME:
					handleJoinGame(msg);
					break;
				case START_GAME:
					handleStartGame();
					break;
				case TEST_MOVE:
					handleTestMove(msg);
					break;
				case SURRENDER_GAME:
					if(this.game != null){ 
						mrw.send(msg);
						this.game.sendMessage(0, this.name + " hat, wie es scheint, die Löffel hängen lassen!");
						this.goToLobby();
					}
					break;
				case LOGOUT:
					this.kill();
					break;
				case PING:
					this.server.pingReset(this);
					break;
				}

			} catch (EndOfTheStreamException e) {
				//this.kill();
				
			} catch (IOException e) {
				//this.kill();
				
			}

		}

	}
	
	/**
	 * <h3>The message handlers</h3>
	 *
	 **/
	 


	/**
	 * Handle the SET_NAME message. If the name is unique, set the new name, and send the id to the client.
	 * @param msg 
	 */
	synchronized private void handleSetName(TozenProtocolMessage msg) {
		if(this.server.isUnuiquePlayerName(msg.players.get(0).name)){
			this.name = msg.players.get(0).name;
			
			msg.players.get(0).id = this.id;
			this.mrw.send(msg);
			
			//It is the first SET_NAME, similar to a login..
			if(this.place == PlaceEnum.LOGIN) {
				this.goToLobby();
			}
			
			this.server.sendPlayersList();
			this.server.sendGamesList();	
		}
	}
	
	
	synchronized private void handleSetGame(TozenProtocolMessage msg) {
		if (this.place == PlaceEnum.LOBBY) { 
			this.game = this.server.createGame(msg.games[0].id, msg.games[0].name,
					this);
			
			this.place = PlaceEnum.GAME;
			this.game.sendGameInfos();
		}

	}
	
	synchronized  private void handleMessage(TozenProtocolMessage msg) {
		//Special messages
		if(msg.message.equals("/game statistics") && this.game != null){
			this.game.sendGameStatstics(this);
			return;
		}
		
		if(msg.message.startsWith("/move") && this.game != null){
			String s[] = msg.message.split(" ");
			this.game.move(Integer.parseInt(s[1]),Integer.parseInt(s[2]),Integer.parseInt(s[3]),Integer.parseInt(s[4]));
			return;
		}
		
		if(msg.message.startsWith("/settile") && this.game != null){
			String s[] = msg.message.split(" ");
			
			int plid = 0;
			for(PlayerThread p : this.server.players.values()){
				if(p.name.equalsIgnoreCase(s[3]))
					plid = p.id;
			}
			if(plid != 0)
				this.game.addTile(Integer.parseInt(s[1]), Integer.parseInt(s[2]), plid, TileEnum.valueOf(s[4]));
			else
				this.sendInfoMessage(s[3] + " doesn't exist!");
			
			return;
		}
		
		/* ************** */
		
		if (msg.players.get(0).id >= 0) {
			this.server.sendMessage(this.id, msg.players.get(0).id, msg.message);
			this.server.sendMessage(this.id, this.id, msg.message);
		} else if (msg.players.get(0).id == -2) 
			this.server.sendMessage(this.id, msg.message);
		else if (msg.players.get(0).id == -1 && game != null) 
			this.game.sendMessage(this.id, msg.message);
		else {
			System.out
					.println("A Message with a invalid receiver was received from Player + "
							+ this.id);
			msg.status = false;
		}
		//mrw.send(msg);
	}
	
	synchronized  private void handleJoinGame(TozenProtocolMessage msg) {
		try {
			this.sendProtocolMessage(msg);
			this.server.joinGame(this, msg.games[0].id);
		} catch (Exception e) {
			e.printStackTrace();
		} 	
	}
	
	synchronized public void handleStartGame() {
		if(this.game.getAdmin() != this)
			this.sendInfoMessage("DU BIST NICHT ADMIN DIESES SPIELS. Will heissen: Nix start..!");
		else
			try {
				this.game.startGame();
			} catch (NotRightPlayerCountException e) {
				this.sendInfoMessage("Überzeuge mehr Spieler, an deinem Spiel teilzunehmen. Die Mindestbelegung wurde noch nicht erreicht.");
				
			} catch (GameAlreadyStartedException e) {
				this.sendInfoMessage("Aha. Eine neue Zeitebene. DAS GEHT NID! Du kannst ein Spiel nur einmal starten.");
				
			}
	}
	
	synchronized  public void handleTestMove(TozenProtocolMessage msg) {
		if(this.game !=  null)
			this.game.tryMove(msg.startX, msg.startY, msg.targetX, msg.targetY);		
	}
}
